/**
 * @module api
 */
import Mmbs from 'mmbs'
import { DefPageSize } from '@/config'

let cacheCollection = {}
let configData = null
let dictionary = {}
let dictionaryCollectionName = 'sys_dictionary'

let _dictionaryList_ = []
let _dictionary_ = {
  loading: false,
  context: []
}
const findCode = (code) => _dictionaryList_.filter(v => v.parentCode === code)
const init = (code) => {
  return new Promise((resolve, reject) => {
    if (_dictionary_.loading) {
      _dictionary_.context.push({
        code: code,
        back: [resolve, reject]
      })
    } else {
      _dictionary_.loading = true
      commonApi
        .search('sys_dictionary', { limit: 1000, ascending: 'order' })
        .then(res => {
          _dictionaryList_ = res
          _dictionary_.context.forEach(item => {
            let results = findCode(item.code)
            item.back[0] && item.back[0](results)
          })
          resolve(findCode(code))
        })
        .catch(e => {
          _dictionary_.context.forEach(item => {
            item.back[1] && item.back[1](e)
          })
          reject(e)
        })
    }
  })
}

/**
 * 通用api操作方法集
 */
export const commonApi = {
  /**
   * 新增数据
   * @function
   * @name save
   * @param {String} collectionName 集合名 {string} 集合名称
   * @param {object} obj 保存的对象
   * @param {object} acl acl访问控制选项
   * @return {Promise}
   */
  save(collectionName, obj, acl = {}, setAcl = true) {
    let instance = this.createInstance(collectionName, obj)
    !setAcl || instance.setACL(this.createACL(acl))
    return instance.save()
  },
  /**
   * 更新数据
   * @function
   * @name update
   * @param {String} collectionName 集合名 表名
   * @param {String} objectId 数据id
   * @param {Object} data 对象
   * @return {Promise}
   */
  update(collectionName, objectId, data) {
    let instance = this.createInstance(collectionName, objectId)
    return instance.save(data)
  },
  /**
   * 删除数据
   * @function
   * @name delete
   * @param {String} collectionName 集合名 {String} - 表名
   * @param objectId {String} - 对象id
   * @return {Promise}
   */
  delete(collectionName, objectId) {
    let instance = this.createInstance(collectionName, objectId)
    return instance.destroy()
  },
  /**
   * 根据ID获取数据
   * @function
   * @name findById
   * @param {String} collectionName 集合名 表名
   * @param objectId 数据id
   * @param include 指针列表
   * @return {PromiseLike<{id: *}> | Promise<{id: *}> | *}
   */
  findById(collectionName, objectId, include) {
    let query = this.createQuery(collectionName)
    query = this.extendQueryInclude(query, include)
    return query.get(objectId).then(v => {
      let { createdAt, updatedAt, ...attributes } = v.attributes
      return Object.assign(v, { ...attributes })
    })
  },
  /**
   * 查询，分页，条件
   * @function
   * @name query
   * @param {String} collectionName 集合名 {String} - 表名
   * @param {Object} options - 选项，示例：{page: 1, rows: 15, _order: '-createdAt', include: [], params: {}}
   * @param {Object} options.params - 参数，示例：{playerName: 'Stan', minClassObj: {value: 60, field: 'obj', type: 'greaterThan'}}
   * @return {Promise}
   */
  query(collectionName, options = {}) {
    let { page = 1, rows = DefPageSize, _order = '-createdAt', include = [], params = {} } = options
    let query = this.createQuery(collectionName)
    query = this.extendQueryOrder(query, _order)
    query = this.extendQueryInclude(query, include)
    query = this.extendQueryParams(query, params)
    return new Promise((resolve, reject) => {
      Promise.all([query.count(), query.limit(rows).skip((page - 1) * rows).find()]).then(results => {
        let count = results[0]
        resolve({
          result: {
            total: count,
            rows: results[1].map(v => {
              let { createdAt, updatedAt, ...attributes } = v.attributes
              return Object.assign(v, { ...attributes })
            })
          }
        })
      }).catch(reject)
    })
  },
  /**
   * 查找列表所有记录
   * @function
   * @name find
   * @param {String} collectionName 集合名 {String} - 表名称
   * @param {Object} options - 查询选项：include，limit, _order, params
   * @return {Promise}
   */
  find(collectionName, options = {}) {
    let { limit = 999, include = [], _order = '-createdAt', params = {} } = options
    let query = this.createQuery(collectionName)
    query = this.extendQueryParams(query, params)
    query = this.extendQueryInclude(query, include)
    query = this.extendQueryOrder(query, _order)
    query.limit(limit)
    query.skip(0)
    return query.find().then(res => res.map(v => {
      let { createdAt, updatedAt, ...attributes } = v.attributes
      return Object.assign(v, { ...attributes })
    }))
  },
  /**
   * 查询，分页，mmbs原生查询数据
   * @function
   * @name query
   * @param collectionName
   * @param options
   * @return {Promise}
   */
  search(collectionName, options = {}, findCount = false) {
    let query = this.createQuery(collectionName)
    query = this.extendQueryOptions(query, options)
    const fn = (array) => (array.map(v => ({ id: v.id, ...v.attributes })))
    if (findCount) {
      return Promise.all([query.count(), query.find()]).then(res => {
        return {
          result: {
            total: res[0],
            rows: fn(res[1])
          }
        }
      })
    } else {
      return query.find().then(fn)
    }
  },
  /**
   * 查询，分页，mmbs原生查询数据查询第一条数据
   * @function
   * @name query
   * @param collectionName
   * @param options
   * @return {Promise}
   */
  searchFirst(collectionName, options = {}) {
    let query = this.createQuery(collectionName)
    query = this.extendQueryOptions(query, options)
    return query.first().then(v => v ? ({ id: v.id, ...v.attributes }) : v)
  },
  /**
   * 根据ID获取数据 打散数据
   * @function
   * @name findById
   * @param {String} collectionName 集合名 表名
   * @param objectId 数据id
   * @param include 指针列表
   * @return {PromiseLike<{id: *}> | Promise<{id: *}> | *}
   */
  searchById(collectionName, objectId, options) {
    let query = this.createQuery(collectionName)
    query = this.extendQueryOptions(query, options)
    return query.get(objectId).then(v => ({ id: v.id, ...v.attributes }))
  },
  /**
   * 根据查询条件获取满足条件的记录总数
   * @function
   * @name searchCount
   * @param {String} collectionName 集合名 表名
   * @param options 查询参数
   * @return {number}
   */
  searchCount(collectionName, options) {
    let query = this.createQueryIncludeOptions(collectionName, options)
    return query.count()
  },
  /**
   * 聚合查询-统计数据
   * @function
   * @name agg
   * @param {String} collectionName 集合名 {String} - 表名称
   * @param params 查询参数
   * @param pipeline
   * @return {Promise}
   */
  agg(collectionName, params, pipeline) {
    let query = this.createQuery(collectionName)
    query = this.extendQueryParams(query, params)
    return query.aggregate(pipeline)
  },
  /**
   * 数据订阅
   * @function
   * @name liveQuery
   * @param {String} collectionName 集合名称
   * @param {Object} params 参数
   * @return {Object} subscribe 监听对象
   */
  liveQuery(collectionName, params) {
    let query = this.createQuery(collectionName)
    query = this.extendQueryParams(query, params)
    return query.subscribe()
  },
  /**
   * 数据订阅
   * @function
   * @name liveQueryIncludeOptions
   * @param {String} collectionName 集合名称
   * @param {Object} options 参数
   * @return {Object} subscribe 监听对象
   */
  liveQueryIncludeOptions(collectionName, options) {
    let query = this.createQuery(collectionName)
    query = this.extendQueryOptions(query, options)
    return query.subscribe()
  },
  /**
   * 获取字典数据
   * @function
   * @name dictionary
   * @param code {String} 唯一标识
   * @return {Promise<Array>}
   */
  dictionary(code) {
    return new Promise((resolve, reject) => {
      let results = findCode(code)
      if (results.length === 0) {
        init(code).then(resolve, reject)
      } else {
        resolve(results)
      }
    })
  },
  /**
   * 获取多个字典数据
   * @function
   * @name dictionaries
   * @param codeList {Array} 唯一标识列表
   * @return {Promise<Array>}
   */
  dictionaries(codeList) {
    return new Promise(resolve => {
      let obj = {}
      const options = {
        limit: 10000,
        _order: 'createdAt',
        params: { 'parentCode': { 'type': 'containedIn', 'value': codeList } }
      }
      this
        .find(dictionaryCollectionName, options)
        .then(results => {
          results.forEach(v => {
            if (!obj[v.parentCode]) {
              obj[v.parentCode] = []
            }
            obj[v.parentCode].push(v)
          })
        })
        .catch(e => ([]))
        .then(() => {
          resolve(codeList.map(v => obj[v] || []))
        })
    })
  },
  /**
   * 将include扩展到query对象上
   * @function
   * @name extendQueryInclude
   * @param {Object} query Mmbs.Query对象
   * @param {Array} include 指针类型属性名集合
   * @return {Object} Mmbs.Query
   */
  extendQueryInclude(query, include) {
    include = include || []
    if (include && include.length) {
      include.forEach(field => (query.include(field)))
    }
    return query
  },
  /**
   * 将排序扩展到query对象上
   * @function
   * @name extendQueryOrder
   * @param query query对象
   * @param order 排序字段
   * @return {Object} Mmbs.Query
   */
  extendQueryOrder(query, order) {
    order = order || '-createdAt'
    if (order.indexOf('-') === 0) {
      query.descending(order.replace('-', ''))
    } else {
      query.ascending(order)
    }
    return query
  },
  /**
   * 将查询扩展到query对象上
   * @function
   * @name extendQueryParams
   * @param {Object} query query对象
   * @param {Object} params 查询参数；
   参数示例；
   一般示例：{name: '关键字', sex: {type: 'equalTo', field: 'sex', value: '男'}, fuzzy: {type: 'crossField', field: ['compony', 'address', 'keywords'], value: '关键字'}}。
   “与”/“或”示例：{or: [{field: 'name', type: 'equalTo', value: 'keyWord'}, {field: 'compony', type: 'equalTo', value: 'keyWord'}]}。
   “与”/“或”示例2：{or: {'name': '模糊匹配', 'company': {type: 'equalTo', field: 'company', value: '公司名称'}}}。
   * @return {Object} Mmbs.Query
   */
  extendQueryParams(query, params) {
    params = params || {}
    /**
     * 根据条件创建Query对象，用于 and 和 or
     * @param {String} className 类型名
     * @param {Object|Array} params 参数对象，数组或对象
     * @returns {Array} Mmbs.Query对象集合
     */
    let createQuerys = (className, params) => {
      let querys = []
      // 数组
      if (params instanceof Array) {
        for (let i = 0; i < params.length; i++) {
          const { type, field, value } = params[i]
          if (field) {
            let curQuery = this.createQuery(className)
            curQuery[type] && curQuery[type](field, value)
            querys.push(curQuery)
          }
        }
      } else if (params instanceof Object) { // 对象类型
        for (const key in params) {
          if (params.hasOwnProperty(key)) {
            const item = params[key]
            let curQuery = this.createQuery(className)
            if (typeof item === 'string') {
              curQuery.contains(key, item)
            } else {
              curQuery = fn(curQuery, item, key)
            }
            querys.push(curQuery)
          }
        }
      }
      return querys
    }
    // 扩展条件给query，并返回Mmbs.Query对象
    const fn = (query, v, defaultField) => {
      let { type, field, value } = v
      // 集合名
      let className = query && query.className
      if (type && query[type]) {
        query[type](field || defaultField, value)
      } else if (type === 'crossField' && value) {
        if (typeof field === 'string') {
          field = field.split(',')
        }
        // 或查询/跨字段模糊匹配
        let querys = []
        for (let i = 0; i < field.length; i++) {
          const curField = field[i]
          if (curField) {
            let curQuery = this.createQuery(className)
            curQuery.contains(curField, value)
            querys.push(curQuery)
          }
        }
        query = Mmbs.Query.or(...querys)
      } else if (['and', 'or'].indexOf(defaultField) >= 0) {
        // And 和 Or 查询；示例：{or: [{field: 'name', type: 'equalTo', value: 'keyWord'}, {field: 'compony', type: 'equalTo', value: 'keyWord'}]}
        query = Mmbs.Query[defaultField](...createQuerys(className, v))
      }
      return query
    }
    for (let key in params) {
      let val = params[key]
      if (!val) continue
      if (typeof val === 'string') {
        query.contains(key, val)
      } else if (val instanceof Array) {
        if (['and', 'or'].indexOf(key) >= 0) {
          query = fn(query, val, key)
        } else {
          val.forEach(v => {
            query = fn(query, v, key)
          })
        }
      } else if (typeof val === 'object') {
        query = fn(query, val, key)
      }
    }
    return query
  },
  /**
   * 将查询扩展到query对象上
   * @function
   * @name extendQueryOptions
   * @param query query对象
   * @param options mmbs默认参数查询
   * @return {Object} Mmbs.Query
   */
  extendQueryOptions(query, options) {
    const extendQuery = (query, fn, val) => {
      if (val instanceof Array) {
        val.forEach(v => query[fn](v))
      } else if (typeof val === 'object' && query[fn] && val) {
        for (let field in val) {
          query[fn](field, val[field])
        }
      } else {
        query[fn](val)
      }
      return query
    }
    const getQuery = (query, options) => {
      for (let fn in options) {
        let obj = options[fn]
        if (['and', 'or'].indexOf(fn) > -1 && Array.isArray(obj)) {
          let results = obj.map(item => getQuery(this.createQuery(query.className), item))
          query = Mmbs.Query[fn](...results)
        } else {
          query = extendQuery(query, fn, obj)
        }
      }
      return query
    }
    return getQuery(query, options)
  },
  /**
   * 创建一个类
   * @function
   * @name createCollection
   * @param {String} collectionName 集合名
   * @return {Object} Mmbs.Object
   */
  createCollection(collectionName) {
    let Collection = cacheCollection[collectionName]
    if (!Collection) {
      Collection = Mmbs.Object.extend(collectionName)
      cacheCollection[collectionName] = Collection
    }
    return Collection
  },
  /**
   * 创建类的query实例
   * @function
   * @name createQuery
   * @param {String} collectionName 集合名
   * @return {Object} Mmbs.Query
   */
  createQuery(collectionName) {
    let Collection = this.createCollection(collectionName)
    return new Mmbs.Query(Collection)
  },
  /**
   * 创建类的query实例 包含查询条件
   * @function
   * @name createQuery
   * @param {String} collectionName 集合名
   * @param {Object} options Mmbs的query选项
   * @return {Object} Mmbs.Query
   */
  createQueryIncludeOptions(collectionName, options) {
    let query = this.createQuery(collectionName)
    return this.extendQueryOptions(query, options)
  },
  /**
   * 创建实体
   * @function
   * @name createInstance
   * @param {String} collectionName 集合名
   * @param {String|Object} obj 对象数据或者id
   * @return {Object} Mmbs.Object 数据实例
   */
  createInstance(collectionName, obj) {
    if (!obj) return null
    let Collection = this.createCollection(collectionName)
    obj = typeof obj === 'string' ? { id: obj } : obj
    return new Collection(obj)
  },
  /**
   * 添加acl权限
   * @function
   * @name createACL
   * @param {Object} acl acl选项，默认：{aclRead: "public", aclWrite: "public"}
   * 设置单用户: {aclRead: { users: "TJT8ENTgqg" }, aclWrite: { users: "TJT8ENTgqg" }}
   * 设置多用户 {aclRead: { users: ["TJT8ENTgqg", "G9jI7QOjmz"] }, aclWrite: { users: ["TJT8ENTgqg", "G9jI7QOjmz"] }}
   * 设置单角色: {aclRead: { roles: "administrator" }, aclWrite: { roles: "administrator" }}
   * 设置多角色 {aclRead: { roles: ["administrator", "passenger"] }, aclWrite: { roles: ["administrator", "passenger"] }}
   * @return {Object} Mmbs.ACL
   */
  createACL(acl) {
    let aclObject = new Mmbs.ACL()
    let { aclRead = "public", aclWrite = "public" } = acl || {}
    if (aclRead && typeof aclRead === "object") {
      let { users, roles } = aclRead
      if (typeof users === "string") {
        aclObject.setReadAccess(users, true)
      } else if (Array.isArray(users)) {
        users.forEach(v => aclObject.setReadAccess(v, true))
      }
      if (typeof roles === "string") {
        aclObject.setReadAccess(roles, true)
      } else if (Array.isArray(roles)) {
        roles.forEach(v => aclObject.setRoleReadAccess(v, true))
      }
    } else {
      aclObject.setPublicReadAccess(true)
    }
    if (aclWrite && typeof aclWrite === "object") {
      let { users, roles } = aclWrite
      if (typeof users === "string") {
        aclObject.setWriteAccess(users, true)
      } else if (Array.isArray(users)) {
        users.forEach(v => aclObject.setWriteAccess(v, true))
      }
      if (typeof roles === "string") {
        aclObject.setRoleWriteAccess(roles, true)
      } else if (Array.isArray(roles)) {
        roles.forEach(v => aclObject.setRoleWriteAccess(v, true))
      }
    } else {
      aclObject.setPublicWriteAccess(true)
    }
    return aclObject
  },
  /**
   * 用户登录
   * @function
   * @name login
   * @param {String} username 用户名
   * @param {String} password 密码
   * @return {Promise}
   */
  login({ username, password }) {
    return Mmbs.User.logIn(username, password)
  },
  /**
   * 登出
   * @function
   * @name logout
   * @return {Promise}
   */
  logout() {
    return Mmbs.User.logOut()
  },
  /**
   * 运行云函数
   * @function
   * @name runCloud
   * @param funcName {String} - 函数名称
   * @param data {Object} - 函数参数
   * @return {Promise}
   */
  runCloud(funcName, data) {
    return Mmbs.Cloud.run(funcName, data)
  },
  /**
   * 上传文件
   * @function
   * @name uploadFile
   * @param {Object} file - 文件对象
   * @return {Promise}
   */
  uploadFile(file) {
    let mFile = new Mmbs.File(file.name, file)
    return mFile.save()
  },
  /**
   * graphql查询
   * @function
   * @name gql
   * @param {String} query 查询字符串
   * @param {Object} variables 参数
   * @param {String} operationName 查询操作名
   * @param {Object} options 选项
   * @return {Promise}
   */
  gql(query, variables, operationName, options) {
    return Mmbs.Cloud.gql(query, variables, operationName, options).then(res => (res && res.data))
  },
  /**
   * 获取Mmbs
   * @function
   * @name getMmbs
   * @return {Object} Mmbs
   */
  getMmbs() {
    return Mmbs
  },
  /**
   * 获取Mmbs全局配置
   * @function
   * @name getMmbsConfig
   * @param {String} name 配置名，若为空则返回当前所有配置
   */
  getMmbsConfig(name = '') {
    return name ? configData.get(name) : configData
  },
  /**
   * 获取Mmbs全局配置
   * @function
   * @name initMmbsConfig
   * @return {Promise}
   */
  initMmbsConfig() {
    let replaceConfigHosts = (config) => {
      let intranetConfig = config.get('intranet_hosts_config')
      if (!intranetConfig) {
        return config
      }
      // 若输入地址是内网IP，按配置替换
      let intranetHosts = intranetConfig.intranetHosts
      let replaceFlag = intranetHosts.indexOf(location.host) >= 0
      let replaceHost = intranetConfig.replaceHost
      // 要替的key列表
      let replaceKeys = intranetConfig.replaceKeys
      if (replaceFlag && replaceKeys) {
        replaceKeys.forEach((keyStr, index) => {
          let curitem = config.get(keyStr)
          // 替换key（原ip）为value（内网ip）
          replaceHost.forEach(item => {
            let key = item.key
            let value = item.value
            curitem = curitem.replace(key, value)
            // 替换mmbsURL
            if (index === 0) {
              Mmbs.serverURL = Mmbs.serverURL.replace(key, value)
            }
          })
          config.attributes[keyStr] = curitem
        })
      }
      return config
    }
    return Mmbs.Config.get().then((config) => {
      configData = replaceConfigHosts(config)
      return config
    })
  },
  /**
   * 批量保存
   * @function
   * @name saveAll
   * @param {Array} objects Mmbs对象集合
   * @return {Promise}
   */
  saveAll(objects) {
    return Mmbs.Object.saveAll(objects)
  },
  /**
   * 批量刪除
   * @function
   * @name destroyAll
   * @param {Array} objects Mmbs对象集合
   * @return {Promise}
   */
  destroyAll(objects) {
    return Mmbs.Object.destroyAll(objects)
  },
  /**
   * 反响查询
   * @function
   * @name reverseQuery
   * @param {String} target 目标表
   * @param {String} field 目标字段
   * @param {Object} instance 目标实体
   * @return {Promise}
   */
  reverseQuery(target, field, instance) {
    let query = Mmbs.Relation.reverseQuery(target, field, instance)
    const fn = (array) => (array.map(v => ({ id: v.id, ...v.attributes })))
    return query.find().then(fn)
  },
  /**
   * 批量导入数据
   */
  batchImport(data) {
    let gql = `
      mutation importData($data: JSON!) {
        createObjects (
          input: $data
        )
      }
    `
    return this.gql(gql, { data })
  },
  /**
   * 获取表中一列去重数据
   * @param {string} collectionName // 集合名
   * @param {string} fieldName // 字段名
   */
  distinct(collectionName, fieldName) {
    let query = this.createQuery(collectionName)
    return query.distinct(fieldName)
  },
  /**
   * 根据用户id 获取该数据是否可以被写入
   * @param id 用户id或对象
   * @param obj 数据object
   * @param collectionName 数据表名
   * @returns {Boolean}
   */
  getWriteAccessById(user, obj, collectionName) {
    obj = { ...obj }
    user = typeof user === "object" ? user.id : user
    let instance = this.createInstance(collectionName, obj)
    return instance.getACL().getWriteAccess(user)
  },
  /**
   * 根据用户角色, 获取该数据是否可以被写入
   * @param collectionName 数据表名
   * @param obj 数据object
   * @param roles 用户角色列表 {array}
   * @returns {Boolean}
   */
  getWriteAccessByRoles(roles, obj, collectionName) {
    obj = { ...obj }
    let instance = this.createInstance(collectionName, obj)
    let acl = instance.getACL()
    let write = roles.find(v => acl.getWriteAccess(`role:${ v.name }`))
    return !!write
  },
  /**
   * 根据用户或者角色, 获取该数据是否可以被写入
   * @param user
   * @param roles
   * @param obj
   * @param collectionName
   * @returns {*}
   */
  getWriteAccess(user, roles, obj, collectionName) {
    let instance = this.createInstance(collectionName, { id: obj.id })
    let acl = instance.getACL()
    user = typeof user === "object" ? user.id : id
    let userWirte = acl.getWriteAccess(user)
    let roleWrite = roles.find(v => acl.getWriteAccess(`role:${ v.name }`))
    return userWirte || roleWrite
  }
}
